﻿var TileStatus = {
    TILE_STATUS_INVALID: -1,
    TILE_STATUS_NORMAL: 0,
    TILE_STATUS_SELECTED: 1
};

var LinLinkTile = function () {
    this.Status = TileStatus.PUZZLE_STATUS_NORMAL;
    this.Type = 0;
    this.PosX = -1;
    this.PosY = -1;
    this.Left = 0;
    this.Top = 0;
};

var LinLinkGame = function () {
    this.TILE_WIDTH_MIN = 16;
    this.TILE_WIDTH_MAX = 64;
    this.TILE_HEIGHT_MIN = 20;
    this.TILE_HEIGHT_MAX = 80;

    this.TileNumX = 16;
    this.TileNumY = 12;

    this.BkColor = "#000000";
    this.TileBkColorNormal = "#FFFFFF";
    this.TileBkColorSelected = "#7F7FFF";
    this.TileBdColorNormal = "#7F7F7F";
    this.TileBkColorSelected = "#0000FF";

    var i, j, index;
    var RealTileNumX, RealTileNumY;
    RealTileNumX = this.TileNumX + 2;
    RealTileNumY = this.TileNumY + 2;
    MaxTileNum = RealTileNumX * RealTileNumY;
    this.Tiles = new Array(MaxTileNum);
    this.Colors = new Array('#FF0000', '#00FF00', '#0000FF', '#7F7F7F', '#FF00FF',
        '#FFFF00', '#00FFFF', '#007FFF', '#7F00FF', '#FF7F00', '#FF007F', '#7FFF00',
        '#00FF7F', '#7F7F00', '#007F7F', '#7F007F', '#7F0000', '#007F00', '#00007F');
    this.MaxTileTypeNum = this.Colors.length;

    for (i = 0; i < RealTileNumY; i++) {
        for (j = 0; j < RealTileNumX; j++) {
            index = i * RealTileNumX + j;
            this.Tiles[index] = new LinLinkTile();
            this.Tiles[index].PosX = j;
            this.Tiles[index].PosY = i;
            if (i == 0 || i == RealTileNumY - 1 || j == 0 || j == RealTileNumX - 1)
                this.Tiles[index].Status = TileStatus.TILE_STATUS_INVALID;
        }
    }
    this.idxLastSelected = -1;

    this.GameAreaCanvas = document.getElementById("gamearea_canvas");
    this.ReSize();

    document.addEventListener("click", this, false);
};

LinLinkGame.prototype.ReSize = function () {
    if (this.GameAreaCanvas != null) {
        this.GameAreaCanvas.width = this.GameAreaCanvas.offsetWidth;
        this.GameAreaCanvas.height = this.GameAreaCanvas.offsetHeight;

        this.TileWidth = parseInt(this.GameAreaCanvas.offsetWidth / (this.TileNumX + 2));
        while (this.TileWidth < this.TILE_WIDTH_MIN && this.TileNumX > 1) {
            this.TileNumX = this.TileNumX - 1;
            this.TileWidth = parseInt(this.GameAreaCanvas.offsetWidth / (this.TileNumX + 2));
        }
        if (this.TileWidth > this.TILE_WIDTH_MAX)
            this.TileWidth = this.TILE_WIDTH_MAX;

        this.TileHeight = parseInt(this.GameAreaCanvas.offsetHeight / (this.TileNumY + 2));
        while (this.TileHeight < this.TILE_HEIGHT_MIN && this.TileNumY > 2) {
            this.TileNumY = this.TileNumY - 2;
            this.TileHeight = parseInt(this.GameAreaCanvas.offsetHeight / (this.TileNumY + 2));
        }
        if (this.TileHeight > this.TILE_HEIGHT_MAX) {
            this.TileHeight = this.TILE_HEIGHT_MAX;
        }

        this.GameBoardWidth = this.TileWidth * (this.TileNumX + 2);
        this.GameBoardHeight = this.TileHeight * (this.TileNumY + 2);
        this.GameBoardLeft = (this.GameAreaCanvas.offsetWidth - this.GameBoardWidth) / 2;
        this.GameBoardTop = (this.GameAreaCanvas.offsetHeight - this.GameBoardHeight) / 2;

        var i, j, index;
        var RealTileNumX, RealTileNumY;
        RealTileNumX = this.TileNumX + 2;
        RealTileNumY = this.TileNumY + 2;
        for (i = 0; i < RealTileNumY; i++) {
            for (j = 0; j < RealTileNumX; j++) {
                index = i * RealTileNumX + j;
                this.Tiles[index].Left = this.GameBoardLeft + j * this.TileWidth;
                this.Tiles[index].Top = this.GameBoardTop + i * this.TileHeight;
            }
        }
    }
};

LinLinkGame.prototype.NewGame = function () {
    var i, j, index, type;
    for (i = 1; i <= this.TileNumY / 2; i++) {
        for (j = 1; j <= this.TileNumX; j++) {
            type = Math.floor(Math.random() * this.MaxTileTypeNum);
            index = i * (this.TileNumX + 2) + j;
            this.Tiles[index].Status = TileStatus.TILE_STATUS_NORMAL;
            this.Tiles[index].Type = type;

            index = (this.TileNumY - i + 1) * (this.TileNumX + 2) + j;
            this.Tiles[index].Status = TileStatus.TILE_STATUS_NORMAL;
            this.Tiles[index].Type = type;
        }
    }

    this.ReOrder();

    this.idxLastSelected = -1;
};

LinLinkGame.prototype.ReOrder = function () {
    var i, j, index, item, type;

    for (i = 1; i <= this.TileNumY; i++) {
        for (j = 1; j <= this.TileNumX; j++) {
            index = i * (this.TileNumX + 2) + j;
            if (this.Tiles[index].Status != TileStatus.TILE_STATUS_INVALID) {
                do {
                    item = Math.floor(Math.random() * (this.TileNumX + 2) * (this.TileNumY + 2));
                } while (this.Tiles[item].Status == TileStatus.TILE_STATUS_INVALID);

                if (item != index) {
                    type = this.Tiles[item].Type;
                    this.Tiles[item].Type = this.Tiles[index].Type;
                    this.Tiles[index].Type = type;
                }
            }
        }
    }
};

LinLinkGame.prototype.Draw = function () {
    var ctx = this.GameAreaCanvas.getContext("2d");

    ctx.fillStyle = this.BkColor;
    ctx.fillRect(0, 0, this.GameAreaCanvas.offsetWidth, this.GameAreaCanvas.offsetHeight);

    var i, j, index;
    for (i = 1; i <= this.TileNumY; i++) {
        for (j = 1; j <= this.TileNumX; j++) {
            index = i * (this.TileNumX + 2) + j;
            this.DrawTile(ctx, this.Tiles[index]);
        }
    }
};

LinLinkGame.prototype.DrawTile = function (context, Tile) {
    if (Tile.Status != TileStatus.TILE_STATUS_INVALID) {
        switch (Tile.Status) {
            case TileStatus.TILE_STATUS_NORMAL:
                context.fillStyle = this.TileBkColorNormal;
                context.strokeStyle = this.TileBdColorNormal;
                break;
            case TileStatus.TILE_STATUS_SELECTED:
                context.fillStyle = this.TileBkColorSelected;
                context.strokeStyle = this.TileBdColorSelected;
                break;
        }

        context.fillRect(Tile.Left, Tile.Top, this.TileWidth, this.TileHeight);
        context.strokeRect(Tile.Left + 0.5, Tile.Top + 0.5, this.TileWidth - 1, this.TileHeight - 1);

        this.DrawCrystalBall(context, Tile.Left, Tile.Top, this.TileWidth, this.TileHeight, this.Colors[Tile.Type]);
    }
    else {
        context.fillStyle = this.BkColor;
        context.fillRect(Tile.Left, Tile.Top, this.TileWidth, this.TileHeight);
    }
};

LinLinkGame.prototype.DrawCrystalBall = function (context, Left, Top, Width, Height, Color) {
    function drawEllipse(ctx, x1, y1, x2, y2) {
        var kp = 4 * ((Math.sqrt(2) - 1) / 3), rx = (x2 - x1) / 2, ry = (y2 - y1) / 2, cx = x1 + rx, cy = y1 + ry; ctx.beginPath(); ctx.moveTo(cx, cy - ry);
        ctx.bezierCurveTo(cx + (kp * rx), cy - ry, cx + rx, cy - (kp * ry), cx + rx, cy); ctx.bezierCurveTo(cx + rx, cy + (kp * ry), cx + (kp * rx), cy + ry, cx, cy + ry);
        ctx.bezierCurveTo(cx - (kp * rx), cy + ry, cx - rx, cy + (kp * ry), cx - rx, cy); ctx.bezierCurveTo(cx - rx, cy - (kp * ry), cx - (kp * rx), cy - ry, cx, cy - ry);
        ctx.closePath(); return false;
    };

    var sz = 0, fc = 0.85, lw = 0.4;
    var cx = Left, cy = Top;
    if (Width > Height) {
        sz = Height;
        cx = Left + (Width - Height) / 2;
    }
    else {
        sz = Width;
        cy = Top + (Height - Width) / 2;
    }

    var iw = Math.round(sz * fc), ih = iw, ic = ih / 2, os = (sz - iw) / 2, op = 0;
    var sh = 1, sd = 1;

    context.save();
    context.translate(cx, cy + sz * .64); context.scale(1, 0.425);
    st = context.createRadialGradient(os + ic, ic, 1, os + ic, ic, ic);
    st.addColorStop(0, 'rgba(0,0,0,' + sh + ')');
    st.addColorStop(.8, 'rgba(0, 0, 0,' + (sh * .125) + ')');
    st.addColorStop(1, 'rgba(0,0,0,0)');
    context.fillStyle = st;
    context.fillRect(0, 0, sz, sz);
    context.restore(); context.save();

    context.translate(cx, cy);
    context.beginPath(); context.arc(os + ic, ic, ic, 0, Math.PI * 2, true); context.closePath();
    context.clip();
    context.clearRect(0, 0, sz, sz);

    context.globalAlpha = 1.0;
    context.fillStyle = Color;
    context.fillRect(os, 0, iw + 1, ih + 1);

    context.globalAlpha = 1.0;
    st = context.createLinearGradient(ic, 0, ic + (3 * os), ih);
    st.addColorStop(0, 'rgba(0,0,0,' + (sh * .5) + ')');
    st.addColorStop(1, 'rgba(0,0,0,0)'); context.fillStyle = st;
    context.fillRect(0, 0, sz, sz);

    st = context.createRadialGradient(os + ic, ic, ic - (1.5 * os), os + ic, ic, ic);
    st.addColorStop(0, 'rgba(0,0,0,0)');
    st.addColorStop(1, 'rgba(0,0,0,' + (sh * .5) + ')'); context.fillStyle = st;
    context.fillRect(0, 0, sz, sz);

    st = context.createRadialGradient((3 * os) + ic, (3.5 * os) + ic, os * .5, (2.5 * os) + ic, (3 * os) + ic, ic - os);
    st.addColorStop(0, 'rgba(255,255,255,' + sd + ')');
    st.addColorStop(0.25, 'rgba(255,255,255,' + (sd * .666666) + ')');
    st.addColorStop(1, 'rgba(255,255,255,0)'); context.fillStyle = st;
    context.fillRect(0, 0, sz, sz);

    st = context.createLinearGradient((iw / 2) - os, os * .5, (iw / 2) + os, (os * .5) + ic);
    st.addColorStop(0, 'rgba(255,255,255,' + sd + ')');
    st.addColorStop(1, 'rgba(255,255,255,0)');
    drawEllipse(context, os + (ic / 2.5), os * .5, os + (ic / 2.5) + (ic * 1.2), (os * .5) + (ic * .9));
    context.fillStyle = st; context.fill();

    context.beginPath(); context.arc(os + ic, ic + lw, ic, 0, Math.PI * 2, true); context.closePath();
    context.strokeStyle = 'rgba(0,0,0,' + 1.0 + ')'; context.lineWidth = lw;
    context.stroke(); context.restore();
};

LinLinkGame.prototype.IsConnectedType1 = function (index1, index2) {
    var i, j, index, start, end;

    if (index1 == index2)
        return false;

    if (this.Tiles[index1].PosX == this.Tiles[index2].PosX) {
        if (this.Tiles[index1].PosY < this.Tiles[index2].PosY) {
            start = this.Tiles[index1].PosY;
            end = this.Tiles[index2].PosY;
        }
        else {
            start = this.Tiles[index2].PosY;
            end = this.Tiles[index1].PosY;
        }

        j = this.Tiles[index1].PosX;
        for (i = start + 1; i < end; i++) {
            index = i * (this.TileNumX + 2) + j;
            if (this.Tiles[index].Status != TileStatus.TILE_STATUS_INVALID)
                return false;
        }
        return true;
    }

    if (this.Tiles[index1].PosY == this.Tiles[index2].PosY) {
        if (this.Tiles[index1].PosX < this.Tiles[index2].PosX) {
            start = this.Tiles[index1].PosX;
            end = this.Tiles[index2].PosX;
        }
        else {
            start = this.Tiles[index2].PosX;
            end = this.Tiles[index1].PosX;
        }

        i = this.Tiles[index1].PosY;
        for (j = start + 1; j < end; j++) {
            index = i * (this.TileNumX + 2) + j;
            if (this.Tiles[index].Status != TileStatus.TILE_STATUS_INVALID)
                return false;
        }
        return true;
    }

    return false;
};

LinLinkGame.prototype.IsConnectedType2 = function (index1, index2) {
    var i, j, index;
    j = this.Tiles[index1].PosX;
    i = this.Tiles[index2].PosY;
    index = i * (this.TileNumX + 2) + j;
    if ((this.Tiles[index].Status == TileStatus.TILE_STATUS_INVALID) && this.IsConnectedType1(index1, index) && this.IsConnectedType1(index, index2))
        return true;

    j = this.Tiles[index2].PosX;
    i = this.Tiles[index1].PosY;
    index = i * (this.TileNumX + 2) + j;
    if ((this.Tiles[index].Status == TileStatus.TILE_STATUS_INVALID) && this.IsConnectedType1(index1, index) && this.IsConnectedType1(index, index2))
        return true;

    return false;
};

LinLinkGame.prototype.IsConnectedType3 = function (index1, index2) {
    var i, j, index;
    j = this.Tiles[index1].PosX;
    for (i = 0; i < this.TileNumY + 2; i++) {
        if (i != this.Tiles[index1].PosY && i != this.Tiles[index2].PosY) {
            index = i * (this.TileNumX + 2) + j;
            if ((this.Tiles[index].Status == TileStatus.TILE_STATUS_INVALID) && this.IsConnectedType1(index1, index) && this.IsConnectedType2(index, index2))
                return true;
        }
    }

    i = this.Tiles[index1].PosY;
    for (j = 0; j < this.TileNumX + 2; j++) {
        if (j != this.Tiles[index1].PosX && j != this.Tiles[index2].PosY) {
            index = i * (this.TileNumX + 2) + j;
            if ((this.Tiles[index].Status == TileStatus.TILE_STATUS_INVALID) && this.IsConnectedType1(index1, index) && this.IsConnectedType2(index, index2))
                return true;
        }
    }

    return false;
};

LinLinkGame.prototype.CheckConnection = function (index1, index2) {
    if (index1 != index2 && this.Tiles[index1].Type == this.Tiles[index2].Type) {
        if (this.IsConnectedType1(index1, index2))
            return true;

        if (this.IsConnectedType2(index1, index2))
            return true;

        if (this.IsConnectedType3(index1, index2))
            return true;
    }

    return false;
};

LinLinkGame.prototype.OnClickClientCanvas = function (PosX, PosY) {
    var ctx = this.GameAreaCanvas.getContext("2d");

    if ((PosX >= this.GameBoardLeft) && (PosX < this.GameBoardLeft + this.GameBoardWidth)
        && (PosY >= this.GameBoardTop) && (PosY < this.GameBoardTop + this.GameBoardHeight)) {
        var i, j, index;
        i = Math.floor((PosY - this.GameBoardTop) / this.TileHeight);
        j = Math.floor((PosX - this.GameBoardLeft) / this.TileWidth);
        index = i * (this.TileNumX + 2) + j;

        if (this.Tiles[index].Status != TileStatus.TILE_STATUS_INVALID) {
            this.Tiles[index].Status = TileStatus.TILE_STATUS_SELECTED;

            if (this.idxLastSelected == -1) {
                this.idxLastSelected = index;
                this.DrawTile(ctx, this.Tiles[index]);
            }
            else {
                if (this.CheckConnection(this.idxLastSelected, index)) {
                    this.Tiles[this.idxLastSelected].Status = TileStatus.TILE_STATUS_INVALID;
                    this.Tiles[index].Status = TileStatus.TILE_STATUS_INVALID;

                    this.DrawTile(ctx, this.Tiles[index]);
                    this.DrawTile(ctx, this.Tiles[this.idxLastSelected]);

                    this.idxLastSelected = -1;
                }
                else {
                    if (this.idxLastSelected != index) {
                        this.Tiles[this.idxLastSelected].Status = TileStatus.TILE_STATUS_NORMAL;
                        this.DrawTile(ctx, this.Tiles[this.idxLastSelected]);

                        this.idxLastSelected = index;
                        this.DrawTile(ctx, this.Tiles[index]);
                    }
                }
            }
        }
    }
};

LinLinkGame.prototype.handleEvent = function (evt) {
    switch (evt.type) {
        case 'click':
            switch (evt.target.getAttribute('id')) {
                case 'gamearea_canvas':
                    this.OnClickClientCanvas(evt.offsetX, evt.offsetY);
                    break;
            }
            break;
    }
};

(function () {
    "use strict";

    WinJS.Binding.optimizeBindingReferences = true;

    var app = WinJS.Application;
    var activation = Windows.ApplicationModel.Activation;
    var game = null;

    app.onactivated = function (args) {
        if (args.detail.kind === activation.ActivationKind.launch) {
            if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
                // TODO: This application has been newly launched. Initialize
                // your application here.
                game = new LinLinkGame();
                game.NewGame();
                game.Draw();
            } else {
                // TODO: This application has been reactivated from suspension.
                // Restore application state here.
            }
            args.setPromise(WinJS.UI.processAll());
        }
    };

    app.oncheckpoint = function (args) {
        // TODO: This application is about to be suspended. Save any state
        // that needs to persist across suspensions here. You might use the
        // WinJS.Application.sessionState object, which is automatically
        // saved and restored across suspension. If you need to complete an
        // asynchronous operation before your application is suspended, call
        // args.setPromise().
    };

    app.start();
})();